package com.elitesland.b2c.com.service;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.elitesland.b2c.com.convert.ComAreaConvert;
import com.elitesland.b2c.com.entity.ComAreaDO;
import com.elitesland.b2c.com.entity.QComAreaDO;
import com.elitesland.b2c.com.repo.ComAreaRepo;
import com.elitesland.b2c.com.repo.ComAreaRepoProc;
import com.elitesland.b2c.com.vo.param.ComAreaPageParamVO;
import com.elitesland.b2c.com.vo.resp.ComAreaBaseRespVO;
import com.elitesland.b2c.com.vo.resp.ComAreaDetailRespVO;
import com.elitesland.b2c.com.vo.resp.ComAreaTreeRespVO;
import com.elitesland.b2c.com.vo.save.ComAreaSaveVO;
import com.elitesland.core.base.ApiResult;
import com.elitesland.core.base.PagingVO;
import com.elitesland.core.constant.CommonConstant;
import com.elitesland.core.constant.UdcEnum;
import com.elitesland.core.exception.BusinessException;
import com.elitesland.core.util.TreeDataUtil;
import com.elitesland.system.entity.SysUdcDO;
import com.elitesland.system.service.SysUdcService;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 功能说明
 * </p>
 *
 * @author fred
 * @since 2021-02-01 17:29:32
 */
@Service
@Slf4j
public class ComAreaServiceImpl implements ComAreaService {

    @Autowired
    private JPAQueryFactory jpaQueryFactory;
    @Autowired
    private ComAreaRepo comAreaRepo;
    @Autowired
    private ComAreaRepoProc comAreaRepoProc;
    @Autowired
    private SysUdcService sysUdcService;

    /**
     * 顶级区域
     */
    private final long ROOT_ID = 0L;
    private final String ROOT_NAME = "顶级";

    @Caching(evict = {
            @CacheEvict(cacheNames = "comArea", key = "'tree'")
    })
    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult<Long> save(ComAreaSaveVO saveVO) {
        try {
            checkForSave(saveVO, null);
        } catch (Exception e) {
            log.info("新增区域时校验参数不通过：{}", e.getMessage());
            return ApiResult.fail(e.getMessage());
        }

        // vo转do后保存
        ComAreaDO areaDO = saveVO2DO(saveVO, null);
        comAreaRepo.save(areaDO);

        return ApiResult.ok(areaDO.getId());
    }

    @Caching(evict = {
            @CacheEvict(cacheNames = "comArea", key = "'tree'")
    })
    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult<Long> update(Long id, ComAreaSaveVO saveVO) {
        try {
            checkForSave(saveVO, id);
        } catch (Exception e) {
            log.info("修改区域时校验参数不通过：{}", e.getMessage());
            return ApiResult.fail(e.getMessage());
        }

        // vo转do后保存
        ComAreaDO areaDO = saveVO2DO(saveVO, id);
        comAreaRepo.save(areaDO);

        return ApiResult.ok(id);
    }

    @Caching(evict = {
            @CacheEvict(cacheNames = "comArea", key = "'tree'")
    })
    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult<Long> updateEnable(Long id) {
        if (id == null) {
            return ApiResult.fail("地区ID为空");
        }

        Boolean enabled = comAreaRepoProc.getEnable(id);
        comAreaRepoProc.updateEnable(List.of(id), !enabled);

        return ApiResult.ok(id);
    }

    @Caching(evict = {
            @CacheEvict(cacheNames = "comArea", key = "'tree'")
    })
    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult<List<Long>> updateEnable(List<Long> ids, Boolean enabled) {
        if (CollectionUtils.isEmpty(ids)) {
            return ApiResult.fail("地区ID为空");
        }

        enabled = enabled != null && enabled;

        comAreaRepoProc.updateEnable(ids, enabled);

        return ApiResult.ok(ids);
    }

    @Caching(evict = {
            @CacheEvict(cacheNames = "comArea", key = "'tree'")
    })
    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult<Long> delete(Long id) {
        if (id == null) {
            return ApiResult.fail("区域ID为空");
        }

        comAreaRepoProc.delete(List.of(id));
        return ApiResult.ok(id);
    }

    @Caching(evict = {
            @CacheEvict(cacheNames = "comArea", key = "'tree'")
    })
    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult<List<Long>> delete(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return ApiResult.fail("区域ID为空");
        }

        comAreaRepoProc.delete(ids);
        return ApiResult.ok(ids);
    }

    @Override
    public ApiResult<ComAreaDetailRespVO> get(Long id) {
        if (id == null) {
            return ApiResult.fail("区域ID为空");
        }

        ComAreaDetailRespVO respVO = comAreaRepo.findById(id).map(ComAreaConvert.INSTANCE::do2RespVO).orElse(null);
        if (respVO == null) {
            return ApiResult.fail("区域信息为空");
        }

        // 填充其它信息
        fillRespVO(respVO);

        return ApiResult.ok(respVO);
    }

    @Override
    public ApiResult<Boolean> existsAll(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return ApiResult.<Boolean>fail("地区ID为空").setData(false);
        }

        QComAreaDO qComAreaDO = QComAreaDO.comAreaDO;
        Set<Long> existsIdSet = new HashSet<>(jpaQueryFactory.select(qComAreaDO.id)
                .from(qComAreaDO)
                .where(qComAreaDO.id.in(ids))
                .fetch());
        Set<Long> originalIdSet = new HashSet<>(ids);

        boolean exists = existsIdSet.size() == originalIdSet.size();

        return ApiResult.ok(exists);
    }

    @Override
    public ApiResult<PagingVO<ComAreaDetailRespVO>> search(ComAreaPageParamVO paramVO) {
        if (paramVO == null) {
            return ApiResult.fail("查询参数为空");
        }

        // 组织条件参数
        QComAreaDO qComAreaDO = QComAreaDO.comAreaDO;
        Predicate predicate = qComAreaDO.isNotNull();
        if (paramVO.getParentId() != null) {
            predicate = ExpressionUtils.and(predicate, qComAreaDO.parentId.eq(paramVO.getParentId()));
        }
        if (StrUtil.isNotBlank(paramVO.getName())) {
            String name = "%" + paramVO.getName() + "%";
            predicate = ExpressionUtils.and(predicate, qComAreaDO.name.like(name));
        }
        if (paramVO.getEnabled() != null) {
            predicate = ExpressionUtils.and(predicate, qComAreaDO.enabled.eq(paramVO.getEnabled()));
        }
        predicate = ExpressionUtils.and(predicate, qComAreaDO.deleteFlag.eq(CommonConstant.COMMON_DELETE_NO));

        // 查询数据
        Page<ComAreaDO> areaDOPage = comAreaRepo.findAll(predicate, paramVO.getPageRequest());
        List<ComAreaDetailRespVO> areaVOList = do2RespVO(areaDOPage.getContent());

        return ApiResult.ok(PagingVO.<ComAreaDetailRespVO>builder()
                .total(areaDOPage.getTotalElements())
                .records(areaVOList)
                .build());
    }

    @Override
    public ApiResult<List<ComAreaBaseRespVO>> queryByParent(Long parentId, String name) {
        if (parentId == null) {
            parentId = ROOT_ID;
        }
        QComAreaDO qComAreaDO = QComAreaDO.comAreaDO;

        Predicate predicate = qComAreaDO.parentId.eq(parentId);
        if (StrUtil.isNotBlank(name)) {
            name = "%" + name + "%";
            predicate = ExpressionUtils.and(predicate, qComAreaDO.name.like(name));
        }
        predicate = appendCommon(predicate);

        List<ComAreaBaseRespVO> respVOList = jpaQueryFactory.select(qComAreaDO.id, qComAreaDO.name, qComAreaDO.sortNo)
                .from(qComAreaDO)
                .where(predicate)
                .fetch()
                .stream()
                .sorted(Comparator.comparingInt(t -> t.get(qComAreaDO.sortNo)))
                .map(tuple -> {
                    ComAreaBaseRespVO baseVO = new ComAreaBaseRespVO();
                    baseVO.setId(tuple.get(qComAreaDO.id));
                    baseVO.setName(tuple.get(qComAreaDO.name));

                    return baseVO;
                }).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(respVOList)) {
            // 如果不存在数据，则直接返回
            return ApiResult.ok(Collections.emptyList());
        }

        // 判断是否是父节点
        List<Long> ids = respVOList.stream().map(ComAreaBaseRespVO::getId).collect(Collectors.toList());
        Map<Long, Long> childNumMap = comAreaRepoProc.queryChildrenNum(ids);
        respVOList.forEach(t -> t.setParent(childNumMap.getOrDefault(t.getId(), 0L) > 0));

        return ApiResult.ok(respVOList);
    }

    @Cacheable(cacheNames = "comArea", key = "'tree'")
    @Override
    public ApiResult<List<ComAreaTreeRespVO>> queryForTree() {
        QComAreaDO qComAreaDO = QComAreaDO.comAreaDO;
        List<ComAreaTreeRespVO> respVOList = jpaQueryFactory.select(qComAreaDO.id, qComAreaDO.name, qComAreaDO.sortNo,
                qComAreaDO.parentId, qComAreaDO.enabled, qComAreaDO.deleteFlag)
                .from(qComAreaDO)
                .fetch()
                .stream()
                .filter(t -> t.get(qComAreaDO.enabled) && t.get(qComAreaDO.deleteFlag) == CommonConstant.COMMON_DELETE_NO)
                .map(t -> {
                    ComAreaTreeRespVO respVO = new ComAreaTreeRespVO();
                    respVO.setId(t.get(qComAreaDO.id));
                    respVO.setName(t.get(qComAreaDO.name));
                    respVO.setParentId(t.get(qComAreaDO.parentId));
                    respVO.setSortNum(t.get(qComAreaDO.sortNo));

                    return respVO;
                }).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(respVOList)) {
            // 如果数据为空，则直接返回
            return ApiResult.ok(Collections.emptyList());
        }

        // 转换成树形结构数据
        TreeDataUtil<ComAreaTreeRespVO> treeDataUtil = new TreeDataUtil<>(respVOList, ComAreaTreeRespVO::getId,
                ComAreaTreeRespVO::getParentId, ComAreaTreeRespVO::setChildren, Comparator.comparingInt(ComAreaTreeRespVO::getSortNum));
        List<ComAreaTreeRespVO> rootList = (List<ComAreaTreeRespVO>) treeDataUtil.getRoots()
                .stream()
                .filter(t -> ((ComAreaTreeRespVO) t).getParentId() == ROOT_ID)
                .collect(Collectors.toList());
        return ApiResult.ok(rootList);
    }

    @Override
    public ApiResult<Map<Long, String>> queryName(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return ApiResult.<Map<Long, String>>fail("地区ID为空").setData(Collections.emptyMap());
        }

        Map<Long, String> nameMap = comAreaRepoProc.queryNames(ids);

        return ApiResult.ok(nameMap);
    }

    /**
     * 保存前的数据检查
     *
     * @param saveVO 区域数据
     * @param id     区域ID
     */
    private void checkForSave(ComAreaSaveVO saveVO, Long id) {
        Assert.notNull(saveVO, "保存数据为空");

        boolean exists = true;
        // 如果是修改，则判断修改的数据是否存在
        if (id != null) {
            exists = comAreaRepo.existsById(id);
            Assert.isTrue(exists, "更新数据不存在");
        }

        // 检查上级ID，如果为空，则取顶级ID
        if (saveVO.getParentId() == null || saveVO.getParentId() == ROOT_ID) {
            saveVO.setParentId(ROOT_ID);
        } else {
            exists = comAreaRepo.existsById(saveVO.getParentId());
            Assert.isTrue(exists, "上级区域不存在");
        }

        // 同一级下区域名称不能重复
        Assert.notNull(saveVO.getName(), "区域名称为空");
        exists = comAreaRepoProc.existsName(saveVO.getParentId(), saveVO.getName(), id);
        Assert.isFalse(exists, "存在重复的区域名称");

        saveVO.setSortNo(ObjectUtil.defaultIfNull(saveVO.getSortNo(), 0));
        saveVO.setEnabled(ObjectUtil.defaultIfNull(saveVO.getEnabled(), false));
    }

    /**
     * 保存对象VO转DO
     *
     * @param saveVO 区域数据
     * @param id     区域ID
     * @return 数据DO
     */
    private ComAreaDO saveVO2DO(ComAreaSaveVO saveVO, Long id) {
        ComAreaConvert convert = ComAreaConvert.INSTANCE;
        if (id == null) {
            ComAreaDO areaDO = convert.saveVO2DO(saveVO);
            areaDO.setCategory(getCategoryByParentId(areaDO.getParentId()));

            return areaDO;
        }

        Optional<ComAreaDO> optionalAreaDO = comAreaRepo.findById(id);
        ComAreaDO areaDO = optionalAreaDO.orElseThrow();
        if (Objects.equals(saveVO.getParentId(), areaDO.getParentId())) {
            // 如果上级发送改变，则重新生成类型
            areaDO.setCategory(getCategoryByParentId(areaDO.getParentId()));
        }
        convert.saveVO2DO(saveVO, areaDO);

        return areaDO;
    }

    /**
     * 根据上级ID判断当前的分类
     *
     * @param parentId 上级ID
     * @return 分类
     */
    private String getCategoryByParentId(Long parentId) {
        if (parentId == null || parentId == ROOT_ID) {
            return UdcEnum.COM_AREA_CATEGORY_PROVINCE.getValueCode();
        }

        String parentCategory = comAreaRepoProc.getCategory(parentId);
        Assert.notBlank(parentCategory, "未知上级区域所属分类");

        return getChildCategory(parentCategory);
    }

    /**
     * 获取下级分类
     *
     * @param category 当前分类
     * @return 下级分类
     */
    private static String getChildCategory(String category) {
        if (StrUtil.equals(UdcEnum.COM_AREA_CATEGORY_PROVINCE.getValueCode(), category)) {
            return UdcEnum.COM_AREA_CATEGORY_CITY.getValueCode();
        }
        if (StrUtil.equals(UdcEnum.COM_AREA_CATEGORY_CITY.getValueCode(), category)) {
            return UdcEnum.COM_AREA_CATEGORY_COUNTRY.getValueCode();
        }
        if (StrUtil.equals(UdcEnum.COM_AREA_CATEGORY_COUNTRY.getValueCode(), category)) {
            return UdcEnum.COM_AREA_CATEGORY_STREET.getValueCode();
        }
        throw new BusinessException("未获取到有效的地区类型");
    }

    /**
     * 填充respVO信息
     *
     * @param respVO 待填充信息
     */
    private void fillRespVO(ComAreaDetailRespVO respVO) {
        // 获取上级名称
        if (respVO.getParentId() == null || respVO.getParentId() == ROOT_ID) {
            respVO.setParentName(ROOT_NAME);
        } else {
            respVO.setParentName(comAreaRepoProc.getName(respVO.getParentId()));
        }

        // 获取类型名称
        UdcEnum udcCategory = UdcEnum.COM_AREA_CATEGORY_PROVINCE;
        String categoryName = sysUdcService.findOne(udcCategory.getModel(), udcCategory.getCode(), respVO.getCategory())
                .map(SysUdcDO::getValDesc)
                .orElse(null);
        respVO.setCategoryName(categoryName);
    }

    private List<ComAreaDetailRespVO> do2RespVO(List<ComAreaDO> doList) {
        if (CollectionUtils.isEmpty(doList)) {
            return Collections.emptyList();
        }

        // 查询上级名称
        List<Long> parentIds = doList.stream()
                .map(ComAreaDO::getParentId)
                .filter(t -> t != ROOT_ID)
                .distinct()
                .collect(Collectors.toList());
        Map<Long, String> nameMap = comAreaRepoProc.queryNames(parentIds);

        // 查询类型
        UdcEnum udcCategory = UdcEnum.COM_AREA_CATEGORY_PROVINCE;
        Map<String, String> categoryName = sysUdcService.getCodeMap(udcCategory.getModel(), udcCategory.getCode());

        ComAreaConvert convert = ComAreaConvert.INSTANCE;
        return doList.stream()
                .map(t -> {
                    ComAreaDetailRespVO respVO = convert.do2RespVO(t);
                    respVO.setParentName(respVO.getParentId() == ROOT_ID ? ROOT_NAME : nameMap.get(respVO.getParentId()));
                    respVO.setCategoryName(categoryName.get(respVO.getCategory()));

                    return respVO;
                }).collect(Collectors.toList());
    }

    /**
     * 通用查询条件
     *
     * @param predicate 原有条件
     * @return
     */
    private Predicate appendCommon(Predicate predicate) {
        QComAreaDO qComAreaDO = QComAreaDO.comAreaDO;

        return ExpressionUtils.and(predicate, qComAreaDO.enabled.eq(true).and(qComAreaDO.deleteFlag.eq(CommonConstant.COMMON_DELETE_NO)));
    }
}